package com.nulldev.util.VariableAPI.util.random;

import java.security.SecureRandom;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

import com.nulldev.util.VariableAPI.MathUtil;

public class nSecureRandom extends SecureRandom implements AutoCloseable {

	/**
	 * TODO: Prefer no acceleration so we don't use any PRNGs...
	 */

	private static final long serialVersionUID = -6382871203872093632L;
	private final nRandom nr;

	public nSecureRandom() {
		super();
		this.nr = new nRandom(RandomAccelMethod.FASTER_RNG_WELL512) {
			private static final long serialVersionUID = -7465294609818461021L;

			@Override
			public void setAccelMethod(RandomAccelMethod accelMethod) {
			}

			@Override
			public synchronized void setSeed(long seed) {
			}

			@Override
			public void setFastRandom(boolean fastRNG) {
			}
		};
	}

	public nSecureRandom(final boolean fastRNG) {
		super();
		this.nr = new nRandom(fastRNG) {
			private static final long serialVersionUID = -7465294609818461021L;

			@Override
			public void setAccelMethod(RandomAccelMethod accelMethod) {
			}

			@Override
			public synchronized void setSeed(long seed) {
			}

			@Override
			public void setFastRandom(boolean fastRNG) {
			}
		};
	}

	public nSecureRandom(final byte[] seed) {
		this(seed, false);
	}

	public nSecureRandom(final byte[] seed, final boolean acceleration) {
		super(seed);
		this.nr = new nRandom(seed, acceleration) {
			private static final long serialVersionUID = -7465294609818461021L;

			@Override
			public void setAccelMethod(RandomAccelMethod accelMethod) {
			}

			@Override
			public synchronized void setSeed(long seed) {
			}

			@Override
			public void setFastRandom(boolean fastRNG) {
			}
		};
	}

	@Override
	public void nextBytes(final byte[] bytes) {
		if (bytes == null || bytes.length == 0)
			return;
		this.nr.nextBytes(bytes);
	}

	public byte[] bytes(final int length) {
		if (length <= 0)
			return new byte[0];
		final byte[] buffer = new byte[length];
		this.nextBytes(buffer);
		return buffer;
	}

	@Override
	public int nextInt() {
		return nr.nextInt();
	}

	@Override
	public double nextDouble() {
		return nr.nextDouble();
	}

	@Override
	public float nextFloat() {
		return nr.nextFloat();
	}

	@Override
	public int nextInt(int bound) {
		return nr.nextInt(bound);
	}

	@Override
	public boolean nextBoolean() {
		return nr.nextBoolean();
	}

	@Override
	public synchronized double nextGaussian() {
		return nr.nextGaussian();
	}

	@Override
	public long nextLong() {
		return nr.nextLong();
	}

	@Override
	public synchronized void setSeed(byte[] seed) {
		this.nr.setSeed(MathUtil.getLong(seed, 0));
		super.setSeed(seed);
	}

	@Override
	public void setSeed(long seed) {
		if (seed == 0)
			return;
		this.nr.setSeed(seed);
		super.setSeed(seed);
	}

	@Override
	public void close() throws Exception {
		this.nr.close();
	}

	public nRandom getInsecureRandom() {
		return this.nr;
	}

	@Override
	public byte[] generateSeed(int numBytes) {
		return nr.bytes(numBytes);
	}

	public long randLong(final long min, final long max) {
		return this.nr.randLong(min, max);
	}

	@Override
	public DoubleStream doubles() {
		return nr.doubles();
	}

	@Override
	public DoubleStream doubles(double randomNumberOrigin, double randomNumberBound) {
		return nr.doubles(randomNumberOrigin, randomNumberBound);
	}

	@Override
	public DoubleStream doubles(long streamSize) {
		return nr.doubles(streamSize);
	}

	@Override
	public DoubleStream doubles(long streamSize, double randomNumberOrigin, double randomNumberBound) {
		return nr.doubles(streamSize, randomNumberOrigin, randomNumberBound);
	}

	@Override
	public IntStream ints() {
		return nr.ints();
	}

	@Override
	public IntStream ints(int randomNumberOrigin, int randomNumberBound) {
		return nr.ints(randomNumberOrigin, randomNumberBound);
	}

	@Override
	public IntStream ints(long streamSize) {
		return nr.ints(streamSize);
	}

	@Override
	public IntStream ints(long streamSize, int randomNumberOrigin, int randomNumberBound) {
		return nr.ints(streamSize, randomNumberOrigin, randomNumberBound);
	}

	@Override
	public LongStream longs() {
		return nr.longs();
	}

	@Override
	public LongStream longs(long randomNumberOrigin, long randomNumberBound) {
		return nr.longs(randomNumberOrigin, randomNumberBound);
	}

	@Override
	public LongStream longs(long streamSize) {
		return nr.longs(streamSize);
	}

	@Override
	public LongStream longs(long streamSize, long randomNumberOrigin, long randomNumberBound) {
		return nr.longs(streamSize, randomNumberOrigin, randomNumberBound);
	}

}